package com.ivory.valley.util;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.bson.types.ObjectId;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.ivory.valley.pojo.BaseBean;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;

import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.core.Converter;

public class BeanUtil {

	private final static Map<String, BeanCopier> copierMap = new HashMap<String, BeanCopier>();

	public static Object copy(Object source) {
		try {
			return copy(source, source.getClass().newInstance());
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static Object copy(Object source, Object target) {
		try {
			String beanKey = generateKey(source.getClass(), target.getClass());
			BeanCopier copier = copierMap.get(beanKey);
			if (copier == null) {
				copier = BeanCopier.create(source.getClass(), target.getClass(), useConverter(source, target));
				copierMap.put(beanKey, copier);
			}
			copier.copy(source, target, getConverter(source, target));
			return target;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private static boolean useConverter(Object source, Object target) {
		return false;
	}

	private static Converter getConverter(Object source, Object target) {
		return null;
	}

	private static String generateKey(Class<?> class1, Class<?> class2) {
		return class1.toString() + "#" + class2.toString();
	}

	// -----------	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T extends BaseBean> T dbObject2Bean(DBObject dbObject, T bean) {
		if (dbObject == null) {
			return null;
		}
		try {
			Set<String> keys=dbObject.keySet();
			for(String key : keys){
				Object val = dbObject.get(key);
				if(val!=null){
					if(val instanceof BasicDBList){
						BasicDBList dbList=(BasicDBList)val;
						Field field=findField(bean.getClass(), key);
						if(field==null)continue;
						Class<?> clazz=getListActualType(field);
						List list=new LinkedList();
						for(int i=0; i<dbList.size(); i++){
							Object elementVal=dbList.get(i);
							if(elementVal instanceof DBObject){
								list.add(dbObject2Bean((DBObject)elementVal,(BaseBean)clazz.newInstance()));
							}else{
								list.add(elementVal);
							}
						}
						BeanUtils.setProperty(bean, key, list);
					}else if(val instanceof DBObject){
						Field field=findField(bean.getClass(), key);
						if(field==null)continue;
						BeanUtils.setProperty(bean, key, dbObject2Bean((DBObject)val,(BaseBean)field.getType().newInstance()));
					}else{
						BeanUtils.setProperty(bean, key, val);
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return bean;
	}

	@SuppressWarnings("rawtypes")
	public static <T extends BaseBean> DBObject bean2DBObject(T bean, boolean containNull, Set<String> exclusive){
		if (bean == null) {
			return null;
		}
		try {
			DBObject dbObject = new BasicDBObject();
			
			Class<?> cls=bean.getClass();
			while(cls!=BaseBean.class){
				Method[] methods=cls.getDeclaredMethods();
				for(Method method : methods){
					if("get".equals(method.getName().substring(0,3))){
						String key=Character.toLowerCase(method.getName().charAt(3))+method.getName().substring(4);
						Object val=method.invoke(bean);
						if ((!containNull && val == null) ||
							(exclusive!=null && exclusive.contains(key))){
							continue;
						}else if(val instanceof List){
							List valList=(List)val;
							BasicDBList dbList=new BasicDBList();
							for(int i=0; i<valList.size(); i++){
								Object elementVal=valList.get(i);
								if(elementVal instanceof BaseBean){
									dbList.add(bean2DBObject((BaseBean)elementVal,containNull,exclusive));
								}else{
									dbList.add(dbObjectValueType(elementVal));
								}
							}
							dbObject.put(key, dbList);
						}else if(val instanceof BaseBean){
							if(exclusive==null){
								exclusive=new HashSet<String>();
							}
							dbObject.put(key, bean2DBObject((BaseBean)val,containNull,exclusive));
						}else{
							dbObject.put(key, dbObjectValueType(val));	
						}						
					}
				}
				cls=cls.getSuperclass();
			}
			return dbObject;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	private static Field findField(Class<?> clazz, String name) throws NoSuchFieldException, SecurityException{
		Field field=null;
		do{
			try{
				field=clazz.getDeclaredField(name);
			}catch(NoSuchFieldException e){}
			clazz=clazz.getSuperclass();
		}while(field==null && clazz!=BaseBean.class);		
		return field;
	}
	
	private static Class<?> getListActualType(Field field) throws NoSuchFieldException, SecurityException{
		Type type = field.getGenericType();
		if (type instanceof ParameterizedType) {
		   ParameterizedType paramType = (ParameterizedType) type;     
		  return (Class<?>)paramType.getActualTypeArguments()[0];
		}
		return null;
	}
	
	private static Object dbObjectValueType(Object val){
		if(val instanceof BigDecimal){
			return ((BigDecimal) val).doubleValue();
		}
		return val;
	}

	// -----------
	public static ObjectMapper objectMapper;
	public static void setObjectMapper(ObjectMapper objectMapper) {
		BeanUtil.objectMapper = objectMapper;
		configureObjectMapper(BeanUtil.objectMapper);
	}

	public static void bean2Json(OutputStream out, Object value) {
		try {
			BeanUtil.objectMapper.writeValue(out, value);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static String bean2Json(Object value) {
		try {
			return BeanUtil.objectMapper.writeValueAsString(value);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	public static <T> T json2Bean(String value, Class<T> type) {
		try {
			return BeanUtil.objectMapper.readValue(value, type);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	public static void configureObjectMapper(ObjectMapper objectMapper){
		SimpleModule module = new SimpleModule();
		module.addSerializer(ObjectId.class, new JsonSerializer<ObjectId>() {
	        public void serialize(ObjectId value, JsonGenerator jgen, SerializerProvider provider)   
	                throws IOException, JsonProcessingException {
	              	if(value!=null){
	              		jgen.writeString(value.toString());
	              	}else{
	              		jgen.writeNull();
	              	}
	              }
	          });
		objectMapper.registerModule(module);		
	}	
}
